/*******************************************************************************
 * Copyright (c) 2014 Liviu Ionescu.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Liviu Ionescu - initial implementation.
 *******************************************************************************/

package ilg.gnumcueclipse.packs.core.data;

import java.util.List;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;

import ilg.gnumcueclipse.core.Xml;
import ilg.gnumcueclipse.packs.core.jstree.JsArray;
import ilg.gnumcueclipse.packs.core.jstree.JsObject;

/**
 * Very simple parser, to convert any complicated XML into a more regular and
 * compact representation. The output is a tree, with properties and children.
 * <p>
 * Original attributes are turned into properties, keeping the original name;
 * the string content is turned into a special property (Property.XML_CONTENT).
 * <p>
 * Simple (meaning no children) children elements (like <description> for
 * selected nodes) are also turned into properties.
 * <p>
 * Properties are trimmed by the putProperty() function, so no need to do it
 * again when consuming them.
 * <p>
 * All other children elements are turned into children nodes, recursively.
 * 
 */
public class XmlJsGenericParser {

	public static final String PROPERTY_XML_CONTENT = "$CONTENT";

	public XmlJsGenericParser() {
		;
	}

	/**
	 * Callback to allow the application to name the collection elements.
	 * 
	 * @param name
	 * @param el
	 * @return
	 */
	public String isCollection(String name, Element el) {
		return null;
	}

	public String isMalformedCollectionMember(String name, Element el) {
		return null;
	}

	/**
	 * Parse the xml document.
	 * 
	 * @param document
	 *            the xml document generated by the standard dom parser.
	 * @return a tree starting with a ROOT node.
	 */
	public JsObject parse(Document document) {

		Element el = document.getDocumentElement();

		JsObject tree = new JsObject();
		String nodeName = el.getNodeName();

		tree.putProperty(nodeName, parseRecusive(el, null));

		return tree;
	}

	/**
	 * Parse the current xml element and its children.
	 * 
	 * @param el
	 *            the current xml element to parse.
	 * @return a new JS object.
	 */
	private JsObject parseRecusive(Element el, String skipName) {

		String nodeName = el.getNodeName();
		String collectionDef = isCollection(nodeName, el);
		if (collectionDef != null) {
			String arr[] = collectionDef.split("[|]");
			if (arr.length == 2) {
				return parseCollection(el, arr[0], arr[1]);
			} else {
				System.out.println("Internal error, collection definition " + collectionDef);
			}
		}

		JsObject node = new JsObject();

		// Add all element attributes as node properties.
		NamedNodeMap attributes = el.getAttributes();
		if (attributes != null) {
			for (int i = 0; i < attributes.getLength(); ++i) {
				String attributeName = attributes.item(i).getNodeName();
				node.putProperty(attributeName, el.getAttribute(attributeName));
			}
		}

		List<Element> children = Xml.getChildrenElementsList(el);

		for (Element child : children) {
			String childName = child.getNodeName();

			if (childName.equals(skipName)) {
				continue;
			}

			String malformedCollectionDef = isMalformedCollectionMember(childName, el);
			if (malformedCollectionDef != null) {
				String arr[] = malformedCollectionDef.split("[|]");
				if (arr.length == 2) {

					parseMalformedCollection(node, child, arr[0], arr[1]);
					continue;

				} else {
					System.out.println("Internal error, collection definition " + malformedCollectionDef);
				}
			}

			Object value = null;

			List<Element> grandChildren = Xml.getChildrenElementsList(child);
			if (grandChildren.isEmpty()) {

				String content = Xml.getElementContent(child);

				NamedNodeMap childAttributes = child.getAttributes();
				if (childAttributes == null || childAttributes.getLength() == 0) {
					// Add child-less elements as node properties.
					value = content;
				} else {
					// Child-less with attributes create sub-nodes.
					JsObject subNode = new JsObject();
					value = subNode;

					subNode.putNonEmptyProperty(PROPERTY_XML_CONTENT, content);

					for (int i = 0; i < childAttributes.getLength(); ++i) {
						String attributeName = childAttributes.item(i).getNodeName();
						subNode.putProperty(attributeName, child.getAttribute(attributeName));
					}
				}

			} else {

				value = parseRecusive(child, null);

			}

			Object propertyValue = node.getProperty(childName);
			if (propertyValue == null) {

				// The property does not exist, put it as string or object.
				node.putProperty(childName, value);

			} else {

				// If the property already exists, possibly turn it into an array.
				JsArray array = null;

				// Remove from the object before adding to the array.
				node.removeProperty(childName);

				if (propertyValue instanceof JsArray) {
					// If the property is already an array, use it.
					array = (JsArray) propertyValue;
				} else {
					// If the property was not an array, create one.
					array = new JsArray();

					if (propertyValue instanceof JsObject) {
						array.add((JsObject) propertyValue);
					} else if (propertyValue instanceof String) {
						array.add((String) propertyValue);
					}
				}
				node.putProperty(childName, array);

				array.add(value);
			}
		}
		return node;
	}

	private JsObject parseCollection(Element el, String collection, String name) {

		JsObject node = new JsObject();

		List<Element> children = Xml.getChildrenElementsList(el, collection);
		for (Element child : children) {
			Element nameElement = Xml.getFirstChildElement(child, name);
			String content = Xml.getElementContent(nameElement);

			if (node.hasProperty(content)) {
				System.out.println("Collection property " + content + " already defined, ignored.");
				continue;
			}
			node.putProperty(content, parseRecusive(child, name));
		}

		return node;
	}

	private void parseMalformedCollection(JsObject node, Element el, String collection, String name) {

		Object collectionNode = node.getProperty(collection);
		if (collectionNode == null) {
			collectionNode = new JsObject();
			node.putProperty(collection, collectionNode);
		} else if (!(collectionNode instanceof JsObject)) {
			System.out.println("Malformed collection " + collection + " already defined, ignored.");
			return;
		}

		Element nameElement = Xml.getFirstChildElement(el, name);
		String content = Xml.getElementContent(nameElement);

		if (((JsObject)collectionNode).hasProperty(content)) {
			System.out.println("Collection property " + content + " already defined, ignored.");
			return;
		}
		((JsObject)collectionNode).putProperty(content, parseRecusive(el, name));
	}
}
